﻿using System;
using System.Configuration;
using System.IO;
using System.Security.Cryptography;
using ExceptionHandler;

namespace Util
{
    public class Crypto
    {
        public static byte[] IV;
        public static byte[] Key;

        #region Generar Clave y VI

        /// <summary>
        /// Genera los valores del vector de inicialización y la llave pública para la encriptación
        /// y desencriptación de claves.<remarks>Debe ejecutarse una sola vez al iniciar el sistema.</remarks>
        /// </summary>
        public static void generarValores()
        {
            AesManaged aes = new AesManaged();
            aes.GenerateIV();
            aes.GenerateKey();
            ConfigurationManager.AppSettings["IV"] = aes.IV.ToString();
            ConfigurationManager.AppSettings["Key"] = aes.Key.ToString();
        }

        #endregion

        #region Obtener Clave y VI
        /// <summary>
        /// Obtiene la clave de encriptacion AES del sistema
        /// </summary>
        /// <returns></returns>
        public static byte[] ObtenerKey(AESValues valor)
        {
            string aesValue = ConfigurationManager.AppSettings[valor.ToString()];

            if (!String.IsNullOrEmpty(aesValue))
            {
                return GetBytes(aesValue);
            }

            else
                throw new EncryptionParametersNotSetException();
        }

        /// <summary>
        /// Convierte un string a ByteArray
        /// </summary>
        /// <param name="aesValue"></param>
        /// <returns></returns>
        private static byte[] GetBytes(string aesValue)
        {
            System.Text.UTF8Encoding encoder = new System.Text.UTF8Encoding();
            return encoder.GetBytes(aesValue);
        }
        #endregion
        #region Encriptar
        /// <summary>
        /// Encripta una cadena de texto utilizando AES
        /// </summary>
        /// <param name="plainText">Texto a encriptar</param>
        /// <returns>Cadena encriptada</returns>
        static byte[] encryptStringToBytes_AES(string plainText)
        {
            // Check arguments.
            if (plainText == null || plainText.Length <= 0)
                throw new ArgumentNullException("plainText");

            // Declare the streams used
            // to encrypt to an in memory
            // array of bytes.
            MemoryStream msEncrypt = null;
            CryptoStream csEncrypt = null;
            StreamWriter swEncrypt = null;

            // Declare the AesManaged object
            // used to encrypt the data.
            AesManaged aesAlg = null;

            try
            {
                // Create a AesManaged object
                // with the specified key and IV.
                aesAlg = new AesManaged();
                aesAlg.Key = ObtenerKey(AESValues.Key);
                aesAlg.IV = ObtenerKey(AESValues.IV);

                // Create a decrytor to perform the stream transform.
                ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

                // Create the streams used for encryption.
                msEncrypt = new MemoryStream();
                csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write);
                swEncrypt = new StreamWriter(csEncrypt);

                //Write all data to the stream.
                swEncrypt.Write(plainText);

            }
            finally
            {
                // Clean things up.

                // Close the streams.
                if (swEncrypt != null)
                    swEncrypt.Close();
                if (csEncrypt != null)
                    csEncrypt.Close();
                if (msEncrypt != null)
                    msEncrypt.Close();

                // Clear the AesManaged object.
                if (aesAlg != null)
                    aesAlg.Clear();
            }

            // Return the encrypted bytes from the memory stream.
            return msEncrypt.ToArray();
        }
        #endregion
        
        #region Desencriptar
        /// <summary>
        /// Desencripta una cadena de texto encriptada con AES
        /// </summary>
        /// <param name="cipherText">Texto cifrado</param>
        /// <param name="Key">Llave pública</param>
        /// <param name="IV">Vector de inicialización</param>
        /// <returns>Texto desencriptado</returns>
        static string decryptStringFromBytes_AES(byte[] cipherText, byte[] Key, byte[] IV)
        {
            // Check arguments.
            if (cipherText == null || cipherText.Length <= 0)
                throw new ArgumentNullException("cipherText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("Key");

            // TDeclare the streams used
            // to decrypt to an in memory
            // array of bytes.
            MemoryStream msDecrypt = null;
            CryptoStream csDecrypt = null;
            StreamReader srDecrypt = null;

            // Declare the AesManaged object
            // used to decrypt the data.
            AesManaged aesAlg = null;

            // Declare the string used to hold
            // the decrypted text.
            string plaintext = null;

            try
            {
                // Create a AesManaged object
                // with the specified key and IV.
                aesAlg = new AesManaged();
                aesAlg.Key = Key;
                aesAlg.IV = IV;

                // Create a decrytor to perform the stream transform.
                ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

                // Create the streams used for decryption.
                msDecrypt = new MemoryStream(cipherText);
                csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read);
                srDecrypt = new StreamReader(csDecrypt);

                // Read the decrypted bytes from the decrypting stream
                // and place them in a string.
                plaintext = srDecrypt.ReadToEnd();
            }
            finally
            {
                // Clean things up.

                // Close the streams.
                if (srDecrypt != null)
                    srDecrypt.Close();
                if (csDecrypt != null)
                    csDecrypt.Close();
                if (msDecrypt != null)
                    msDecrypt.Close();

                // Clear the AesManaged object.
                if (aesAlg != null)
                    aesAlg.Clear();
            }

            return plaintext;

        }
        #endregion

        public enum AESValues
        {
            Key,
            IV
        }
    }
}
